package jmpo.cls;

import java.util.Arrays;
import java.util.Objects;

/**
 * В некоторых ситуациях нам требуется класс для простого хранения данных.
 * Например нам нужен класс, который должен хранить информацию об авторе
 * статей: его имя (String) и налоговый идентификатор (long).
 * Тогда нам нужно будет создать класс с соответствующими полями и
 * переопределить ряд методов:
 *          hashCode
 *          equals
 *          toString
 * И определить инициализирующий конструктор.
 */
final class Author {
    /**
     * Определим необходимые внутренние данные:
     */
    private final String name;
    private final long taxId;

    /**
     * Зададим инициализирующий конструктор
     */
    public Author(String name, long taxId) {
        this.name = name;
        this.taxId = taxId;
    }

    /**
     * Определим get методы для каждого элемента класса
     */
    String getName() { return this.name; }
    long getTaxId() { return this.taxId; }

    /**
     * Переопределяем стандартные методы Object
     */
    @Override
    public String toString() {
        return String.format("{name: %s; tax_id: %d}", this.name, this.taxId);
    }
    @Override
    public int hashCode() {
        return Objects.hash(this.name, this.taxId);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (null == o) return false;
        if (this.getClass() != o.getClass()) return false;
        var otherAuthor = (Author)o;
        if (!this.name.equals(otherAuthor.name)) return false;
        if (!(this.taxId == otherAuthor.taxId)) return false;
        return true;
    }
}

/**
 * При создании таких классов мы тратим огромное количество усилий
 * на написание однотипного кода, который не содержит существенной 
 * логики.
 * В Java 14 появилась возможность упростить такую работу, определив
 * класс с помощью ключевого слова record. 
 * В этом случае все сервисные методы компилятор напишет за нас.
 */
/**
 * Сразу после имени класса описываем все его поля,
 * всю работу сделает компилятор!
 */
record RecordAuthor(String name, long taxId) {
    static boolean test() {
        var author1 = new RecordAuthor("Иван Иванович Иванов", 1);
        var author2 = new RecordAuthor("Пётр Петрович Петров", 2);
        var author3 = new RecordAuthor("Иван Иванович Иванов", 1);
        var name1 = author1.name(); // получаем компонент просто по его имени
        var taxId1 = author1.taxId();
      
        // для нас уже подготовили методы equals, toString и hashCode
        return 
            author1.equals(author3) 
            && !author1.equals(author2)
            && author1.toString().equals("RecordAuthor[name=Иван Иванович Иванов, taxId=1]");
        
    }
}

/**
 * При необходимости мы можем переопределить стандартные методы самостоятельно.
 * Например, изменить стандартный метод toString
 */
record RecordAuthorToStringOverride(String name, long taxId) {
    @Override
    public String toString() {
        // мы используем те же названия внутренних полей, что в определении
        return String.format("{name: %s; tax_id: %d}", name, taxId);
    }

    public static boolean test() {
        var author = new RecordAuthorToStringOverride("Иван", 1);
        return author.toString().equals("{name: Иван; tax_id: 1}");
    }
}

/**
 * Если для нашего класса входящие параметры не тотальны,
 * то мы можем проверить их в конструкторе. При этом его 
 * можно записать в укороченном виде - убрав описания входящих
 * параметров.
 */
record RecordAuthorUserDefinedConstructor(String name, long taxId) {
    // Compact constructor
    RecordAuthorUserDefinedConstructor {
        if (taxId <= 0)
            throw new IllegalArgumentException("The taxId must be positive integer");
        if (null == name || 0 == name.length())
            throw new IllegalArgumentException("The name can not be empty");
        // нам не нужно явно присваивать данные в компактной записи конструктора, это сделает компилятор
        // this.name = name;
        // this.taxId = taxId;
    }

    public static boolean test() {
        try {
            var author = new RecordAuthorUserDefinedConstructor("", 2);
            System.out.println(author.toString());
            return false;
        } catch(IllegalArgumentException e) {
            return e.getMessage().equals("The name can not be empty");
        } catch(Exception e) {
            return false;
        }
    }
}

/**
 * Мы можем реализовывать интерфейсы в классах-записях.
 */
record RecordAuthorImplements(String name, long taxId) implements Comparable<RecordAuthorImplements> {
    @Override
    public int compareTo(RecordAuthorImplements rha) {
        if (0 != name.compareTo(rha.name)) return name.compareTo(rha.name);
        return Long.compare(taxId, rha.taxId);
    }

    public static boolean test() {
        var author1 = new RecordAuthorImplements("A",1);
        var author2 = new RecordAuthorImplements("B",2);
        var author3 = new RecordAuthorImplements("A",2);
        RecordAuthorImplements[] authors = {author1, author2, author3};
        Arrays.sort(authors);
        return authors[0] == author1 && authors[1] == author3 && authors[2] == author2;
    }
}

public class RecordClass {
    private static void test(boolean expectedTrue) {
        if (expectedTrue) {
            System.out.println("Ok!");
            return;
        }
        throw new RuntimeException();
    }
    public static void main(String[] args) {
        test(RecordAuthor.test());
        test(RecordAuthorToStringOverride.test());
        test(RecordAuthorUserDefinedConstructor.test());
        test(RecordAuthorImplements.test());
    }
}
